/*
 * Log.java	2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.util.Iterator;
import java.util.Properties;
import java.util.regex.Pattern;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Set;
import java.lang.reflect.InvocationTargetException;

/**
 * <p>
 * The main accessor for logging our entry. It implements a singleton design.
 * So there is only one instance of Log in the whole application, by calling the Log.getLog() method,
 * a check is made to see if an instance of Log already exists. If yes it is returned, if not it is initialized.
 * </p>
 * <p>
 * By calling:<br/>
 * Log l = Log.getLog();<br/>
 * l.info(Object);<br/>
 * </p>
 * <p>
 * a property file, dump.properties is searched in the classpath then if not found the defaul one inside package will be taken.
 * Then...<br/>
 * -Transform file into properties<br/>
 * -Load global property<br/>
 * -Find class name of the Dumper to initialize<br/>
 * -Extrapolate property for dumper<br/>
 * -Initialize the Queue<br/>
 * -Start the QueueDumper if necessary<br/>
 *
 * @author Leonardo Celati
 * @version 1.0
 */
@SuppressWarnings("unchecked")
public final class Log implements Constant {

    /**
     * Enum with the status of log
     */
    enum Status {

        DEAD, STARTUP, RUNNING, SHUTDOWN, EMERGENCY, EMPTY,
    }
    /**
     * Our Log holder
     */
    private static Log l = null;
    /**
     * The current status for logger
     */
    private static Status s = Status.DEAD;
    /**
     * The amount of time the Queue has to be unloaded
     */
    private static long dumpInterval;
    /**
     * Service log
     */
    private static boolean serviceLogEnabled;
    /**
     * Hold the implementation of register
     */
    private static Register reg;
    /**
     * Holds our Queue
     */
    private static Queue q;
    /**
     * The class representing the Entry
     */
    private static Class entryClass;
    /**
     * Kill dumper on failure
     */
    private static boolean killOnFailure;

    /**
     * Default constructor, we are implementing singleton
     */
    private Log() {
    }

    /**
     * Return the interval for the Queue to be unloaded
     * @return millisecond
     */
    public static long getDumpInterval() {
        return dumpInterval;
    }

    /**
     * Getter for killOnFailure
     * @return true/false
     */
    public static boolean getKillOnFailure() {
        return killOnFailure;
    }

    /**
     * Main accessor for getting instance of log
     *
     * @return a valid Log
     */
    public static Log getLog() {

        // simple if...we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {
            l = new Log();
            l.init();
        }
        return l;
    }

    /**
     * Private method for init our Log
     */
    private void init() {


        try {
            // declaring our current mode
            s = Status.STARTUP;

            // load property file
            Properties fp = propertyLoader();
            // start global property setting
            entryClass = fp.getProperty(PROP_ROOT + "entry.classname", DEFAULT_ENTRY).getClass();

            // see if service log is enabled
            if (fp.getProperty(PROP_ROOT + "servicelog") != null) {
                serviceLogEnabled = Boolean.parseBoolean(fp.getProperty(PROP_ROOT + "servicelog"));
            } else {
                serviceLogEnabled = DEFAULT_SERVICELOG;
            // end of global property setting

            // get iterator from Properties
            // it will be useful later on...
            // when instantiating a dumper
            }
            Set set = fp.keySet();

            // looking for dumper.name=.....,....,.....
            String[] declaredDumpers = Pattern.compile(",").split(fp.getProperty("dumper.name"));
            // nothing to say...if dumper are found
            // we store them into register
            // if any error occur initializing the dumper we run in emergency mode
            if (declaredDumpers.length > 0) {
                // uhm...some dumper found, let's have a look
                // we need to store them on register
                Dumper[] dumpers = new Dumper[declaredDumpers.length];

                for (int i = 0; i < declaredDumpers.length; i++) {
                    // from the name we can get the type...it is
                    // dump.<name>.classname=....
                    // therefore we need to catch ClassNotFound and any Runtime exceptions
                    try {
                        // load class and its corresponding constructor...
                        Class<?> c = Class.forName(fp.getProperty(PROP_ROOT + declaredDumpers[i] + ".classname"));

                        // building our set of properties to pass to Dumper constructor
                        Properties dp = new Properties();
                        // starting with the name
                        dp.setProperty("name", declaredDumpers[i]);
                        // all the property for this dumper has to start with this root
                        // example if the name of the dumper is xyz then we will look for
                        // dumper.xyz.[prop]
                        String root = Constant.PROP_ROOT + declaredDumpers[i] + ".";

                        // iterate trough our set...
                        Iterator<Object> it = set.iterator();
                        while (it.hasNext()) {
                            // the key to find in this property
                            String key = it.next().toString();
                            // see if we can find 'root' in set
                            if (key.indexOf(root) > -1) {
                                // if yes...we add to our properties
                                dp.put(key, fp.get(key));
                            }
                        }

                        // use reflection for the rest...
                        Constructor<?> cst = c.getConstructor(Properties.class);
                        Dumper dumper = (Dumper) cst.newInstance(new Object[]{dp});
                        // store dumpers in array
                        dumpers[i] = dumper;

                    } catch (ObjectInitializationException e) {                        
                        throw new Exception(e);
                    } catch (ClassNotFoundException e) {
                        throw new Exception("Class dumper has not found");
                    } catch (InvocationTargetException e) {
                        throw new Exception(e.getCause().getMessage());
                    }
                }
                
                // we are going to check if we had some Dumper
                if (dumpers.length < 1) {
                    throw new ObjectInitializationException("dumper size: " + dumpers.length);
                }

                // time to build other objects, first we retrieve a set
                // of property and pass to each method
                Properties pr = new Properties();
                Properties pq = new Properties();
                Properties pqd = new Properties();

                // iterate trough our set...
                Iterator<Object> it = set.iterator();
                keybrowser:
                while (it.hasNext()) {
                    // the key to find in this property
                    String key = it.next().toString();
                    // see if we can find 'register' in set
                    if (key.indexOf(Constant.PROP_ROOT + "register.") > -1) {
                        // if yes...we add to our properties
                        pr.put(key, ((String) fp.get(key)).replaceAll(Constant.PROP_ROOT + "register.", ""));
                        continue keybrowser;
                    }
                    // see if we can find 'queue' in set
                    if (key.indexOf(Constant.PROP_ROOT + "queue.") > -1) {
                        // if yes...we add to our properties
                        //pq.put(key, ((String) fp.get(key)).replaceAll(Constant.PROP_ROOT + "queue.", ""));
                    	pq.put(key.replaceAll(Constant.PROP_ROOT + "queue.", ""), (String) fp.get(key));
                        continue keybrowser;
                    }
                    // see if we can find 'queuedumper' in set
                    if (key.indexOf(Constant.PROP_ROOT + "queuedumper.") > -1) {
                        // if yes...we add to our properties
                        pqd.put(key, ((String) fp.get(key)).replaceAll(Constant.PROP_ROOT + "queuedumper.", ""));
                        continue keybrowser;
                    }
                }

                // Init our Register
                ObjectFactory.initRegister(pr, dumpers);

                // and now...the Queue itself, with if present a desired register classname
                q = ObjectFactory.initQueue(pq);

                // we check if our Queue implements NotCumulable
                // which means we drop our entry directly to Dumper
                // and we do not need to start QueueDumper....
                if (q.isCumulable()) {
                    Thread t = new Thread(ObjectFactory.initQueueDumper(pqd));
                    t.setName("QueueDumper");
                    t.start();
                }

                // now the log is running
                s = Status.RUNNING;
            }

        //} catch (java.lang.reflect.InvocationTargetException e) {
            // if we get here we couldn't intialize
            // some object with reflection so by default we are
            // in emergency state
        //    serviceLog(new ObjectInitializationException("fail to init object:" + e.getMessage()));
        //    s = Status.EMERGENCY;
        } catch (Exception e) {
            // if we get here we couldn't intialize
            // the log properly so by default we are
            // in emergency state
            serviceLog("Log initialization fail: " + e.getMessage());
            s = Status.EMERGENCY;
        }
    }

    /**
     * Load the dump.properties file in the classpath then if not found, default dump_default.properties is taken from the package
     * @return Properties
     */
    private static Properties propertyLoader() {
        // Holder for dump properties file
        InputStream is = null;
        Properties p = new Properties();
        try {            
            // try to see if user has its own properties file
            is = Log.class.getClassLoader().getResourceAsStream(PROP_NAME);
            // then if null, a default one must exist in the package
            if (is == null) {
                is = Log.class.getClassLoader().getResourceAsStream(PROP_DEFAULT);
            // doing some basic transformation
            }
            p.load(is);
            return p;

        } catch (IOException e) {
            return null;
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                // we are doing nothing
            }
        }
    }

    /**
     * The status of the Log
     * @return Status
     */
    public static Status getStatus() {
        return s;
    }

    /**
     * Get all debug level
     * @param tolog The object to be logged
     */
    public void debug(Object tolog) {
        this.add(tolog, new Throwable(), Severity.DEBUG);
    }

    /**
     * Get all info level
     * @param tolog The object to be logged
     */
    public void info(Object tolog) {
        this.add(tolog, new Throwable(), Severity.INFO);
    }

    /**
     * Get all warn level
     * @param tolog The object to be logged
     */
    public void warn(Object tolog) {
        this.add(tolog, new Throwable(), Severity.WARN);
    }

    /**
     * Get all error level
     * @param tolog The object to be logged
     */
    public void error(Object tolog) {
        this.add(tolog, new Throwable(), Severity.ERROR);
    }

    /**
     * Get all fatal level
     * @param tolog The object to be logged
     */
    public void fatal(Object tolog) {
        this.add(tolog, new Throwable(), Severity.FATAL);
    }

    /**
     * Build an Entry from the passed Object and Store it in Queue
     * @param tolog Object to log
     * @param severity The level of Severity
     * @param t A throwable for getting the calle of this error
     */
    private void add(Object tolog, Throwable t, Severity severity) {
        // log holder
        Entry e = null;
        // decide if we want to log a string or an exception
        if (tolog != null && tolog instanceof Exception) {
            e = new Entry(new Exception((Exception) tolog), severity, System.currentTimeMillis());
        } else {
            e = new Entry(tolog, severity, t.getStackTrace()[t.getStackTrace().length - 1].getClassName(), System.currentTimeMillis());
        }

        // next we decide in which case we are running
        // and take further action
        switch (s) {
            case RUNNING: // we can log
                q.push(e);
                break;
            case EMERGENCY:
                serviceLog(e.getLine());
                break;
            case EMPTY:
                serviceLog(e.getLine());
                break;
        }

    }

    /**
     *
     * @return a current implementation of Entry
     */
    private Entry getEntry() {
        try {
            return (Entry) entryClass.newInstance();
        } catch (InstantiationException e) {
            serviceLog(e);
            return null;
        } catch (IllegalAccessException e) {
            serviceLog(e);
            return null;
        }
    }

    /**
     * Facility for logging everything passed, which will be written to System.err. Useful for debugging.
     * @param o The object to write to log
     */
    public static void serviceLog(Object o) {

        // basic check...
        if (!serviceLogEnabled) {
            return;
        }
        // building our entry
        Entry e;
        // check if we want to log instance of Exception or not...
        if (o != null && o instanceof Exception) {
            e = new Entry(new Exception((Exception) o), Severity.SERVICE, System.currentTimeMillis());
        } else {
            Throwable t = new Throwable();
            e = new Entry(o, Severity.SERVICE, t.getStackTrace()[t.getStackTrace().length - 1].getClassName(), System.currentTimeMillis());
        }
        // finally logging
        serviceLog(e);
    }

    /**
     * Facility for logging a passed  Entry which will be written to System.err. Useful for debugging.
     * @param o The object to write to log
     */
    private static <E extends Entry> void serviceLog(E e) {
        System.err.print(e.getLine());
    }

    /**
     * Main for development, to delete in future release
     */
    public static void main(String[] args) {

        Log l = Log.getLog();

        l.info("sample...");
        l.error("ancora sample...");

        try {
            int i = 0;
            while (true) {
                l.error("err:" + i);
                i++;
                Thread.sleep(1000);

            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}